This first tutorial gives you an opportunity to work with:
setting up a persistent unit,
creating in-memory entities and
persisting those entities to a database.
You'll write the code such that the environment creates the database schema based on meta-information (annotations) in your entity classes.
Good luck!
JPA Tutorial 1: Background
In this first tutorial we are going to perform some basic inserts, removes and queries against a database.
JPA allows us to work with entity classes, which are denoted as such using the annotation @Entity or configured in an XML file (we'll call this persistence meta information). When we acquire the Entity Manager Factory using the Persistence class, the Entity Manager Factory finds and processes the persistence meta information.
To work with a database using JPA, we need an Entity Manager. Before we can do that, we need to create an Entity Manager Factory.
To acquire an Entity Manager Factory, we use the class javax.persistence.Persistence. It reads a file called persistence.xml in the META-INF directory. It then creates the named Entity Manager Factory, which processes persistence meta information stored in XML files or annotations (we only use annotations).
Creating an Entity Manager once we have the Entity Manager Factory is simple:
Once we have an Entity Manager, we can ask it to perform several operations such as persisting or removing an entity from the database or creating a query.
Term
Description
javax.persistence.Persistence
This is a class used as an entry point for using JPA. The primary method you'll use on this class is createEntityManagerFactory("someName") to retrieve an entity manager factory with the name "someName". This class requires a file called persistence.xml to be in the class path under a directory called META-INF.
EntityManagerFactory
An instance of this class provides a way to create entity managers. Entity Managers are not multi-thread safe so we need a way to create one per thread. This class provides that functionality. The Entity Manager Factory is the in-memory representation of a Persistence Unit.
EntityManager
An Entity Manager is the interface in your underlying storage mechanism. It provides methods for persisting, merging, removing, retrieving and querying objects. It is not multi-thread safe so we need one per thread. The Entity Manager also serves as a first level cache. It maintains changes and then attempts to optimize changes to the database by batching them up when the transaction completes.
persistence.xml
A required file that describes one or more persistence units. When you use the javax.persistence.Persistence class to look up an named Entity Manager Factory, the Persistence class looks for this file under the META-INF directory.
Persistence Unit
A Persistence Unit has a name and it describes database connection information either directly (if working in a JSE environment) or indirectly by referencing a JNDI-defined data source (if working in a managed/JEE environment). A Persistence Unit can also specify the classes(entities) it should or should not manage .
Persistence Meta Information
Information describing the configuration of entities and the database and the association between entity classes and the persistence units to which they relate. This is either through annotations added to classes or though XML files. Note that XML files take precedence over annotations.
JPA Initial Setup
This example requires Java 5 (JDK 1.5) or later and Eclipse 3.2 or later. This page gives you a link to all of the downloads you'll need to get to get started. While I might mention specific version numbers, it's a good bet that newer versions should work as well... of course that's not always the case.
Note: We need the jar file that contains javax.persistence and the various annotations associated with JPA. You can either download JEE or get it from somewhere else. For this series of tutorials, we eventually use the JBoss EJB3 Embeddable container, so we'll use that to avoid an extra 150+ meg download for one jar file.
Download Everything
First the basic stuff:
Download JSE 5 (choose just JDK 5.0 (there will be an update after it))
Extract the eclipse download somewhere. For all examples I use C:/eclipse.
Extract Jar Files
Extract each of the libraries to some location. In my case I extracted everything to C:/libs, so I have the following directories
C:/libs/hibernate-annotations-3.4.0.GA
C:/libs/hibernate-entitymanager-3.4.0.GA
C:/libs/hibernate-distribution-3.3.1.GA
C:/libs/hsqldb-1.9.0-beta3
C:/libs/openejb-3.1.1
C:/libs/slf4j-1.5.8
Eclipse Project Setup
Next we need to start eclipse and create a workspace.
Create Initial Project
Start eclipse.
When prompted, enter a directory for your workspace. I used C:\workspaces\JpaAndEjb3. To understand why I recommend not using a space in the name, read this sidebar.
Close the Welcome window
User Library
We are going to define a user library, which is just a collection of jar files with a name. Once we create this, we can add it to our classpath with one command. This also makes setting up new projects in the same workspace a snap. We can also export workspace settings and import them into a new workspace.
Pull down Window:Preferences
Navigate to Java:Build Path:User Libraries
Click on New
Enter JPA_JSE for the name and click on OK
Now we need to add several jars to this list. For each of the following jars, do the following:
Select JPA_JSE (after you add the first one, you'll have to go back and click the library, which seems to be a poor UI design)
Click on Add JARs...
Navigate to the jar file
Select the jar file
Click on Open
Repeat at step one.
Here is a list of all the jar files you'll need to add (note the path's listed assume you extracted your jar files to C:/libs):
Next we need to create a Java project. We'll keep the source separate from the bin directory:
Pull down the File menu and select New:Project
Select Java Project and click Next
Enter a project name: JpaTutorial1, again read this sidebar to know why I did not use a space in the project name.
Make sure "Create new project in workspace" is selected.
Make sure the JRE selected is 1.5.x. If a 1.5 JRE does not show in the list, you can add it through Window->Preferences->JAVA->Installed JRE's.
Select "Create separate source and output folders"
Click "finish"
Create folders and packages
Expand your project JpaTutorial1
Select the src folder
Right-click, select new:Folder
Enter the name META-INF
Click Finish
Select the src folder again
Right-click, select new:Package
Enter the name entity
Click on Finish
Select the Tutoria1 project again
Right-click, select new:Source Folder
Enter the name test
Click Finish
Select the test folder
Right-click, select new:Package
Enter the name entity
Add Required Libraries
We now need to add two libraries. One will be the user-defined library we created above. The second will be JUnit 4.x.
Edit the project properties. Select your project (e.g. JpaTutorial1) and either press alt-enter or right-click and select properties.
Select Java Build Path
Click on the Libraries tab
Click on Add Library
Select User Libraries and click Next
Select JPA_JSE by clicking on the checkbox
Click OK
Click on Add Library again
Click on JUnit
Click Next
In the pull-down list, select JUnit 4
Click Finish
Click OK
Sidebar
The JPA specification says that in a managed environment (read as running in the container), you do not need to list your entity classes in the persistence.xml (this is coming up). When you're using JPA in a JSE environment, this is not guaranteed. In all of these examples, we're using the hibernate implementation of the JEE Entity Manager. It provides the functionality of automatically registering all of your entities without your having to explicitly list them. However, if you happen to have a space in your class path, it appears to fail.
If you followed the instructions above, you'll have the following directory: C:/Workspaces/JpaAndEjb3/JpaTutorial1. Under that directory will be a bin directory where our compiled class files reside. Hibernate will look in this directory and find all classes that are entities (denoted with @Entity) and add those to our persistent unit. In the first version of this tutorial, I recommended the following name: C:/Workspaces/Jpa And Ejb3/Tutorial 1. When I ran the driver program, hibernate was unable to automatically find the bin directory, I assume this was because of the spaces in the name. I changed the name by removing all of the spaces and the problem went away.
Persistence Unit Configuration
We now need to create the Persistent Unit definition. Create a file called persistence.xml in the src/META-INF directory with the following contents:
For this example we'll use a "top-down" approach. This means we'll create a Plain Old Java Object (POJO) with some annotations to indicate how we want JPA to persist it. We're letting the EntityManager take care of creating the tables in the database for us.
Create a Simple Class
The following class contains everything you need to begin persisting it to a database: Person.java
Note, for our configuration this step is optional.
If you use libraries provided exclusively by JBoss and Company, then you do not need to update your persistence.xml. If you are using another vendor or you want to make sure that your solution will work regardless of your persistence provider, add the following line to your persistence.xml:
Now we need to update our unit test class, Person.java. We will have it insert two people, query and verify that the people we created are in the database: PersonTest.java
packageentity;importstaticorg.junit.Assert.assertEquals;importstaticorg.junit.Assert.assertTrue;importjava.util.List;importjavax.persistence.EntityManager;importjavax.persistence.EntityManagerFactory;importjavax.persistence.Persistence;importorg.apache.log4j.BasicConfigurator;importorg.apache.log4j.Level;importorg.apache.log4j.Logger;importorg.junit.After;importorg.junit.Before;importorg.junit.Test;publicclass PersonTest {privatefinal Person p1 = new Person("Brett", 'L', "Schuchert", "Street1",
"Street2", "City", "State", "Zip");privatefinal Person p2 = new Person("FirstName", 'K', "LastName",
"Street1", "Street2", "City", "State", "Zip");private EntityManagerFactory emf;private EntityManager em;
@Before
publicvoid initEmfAndEm(){
BasicConfigurator.configure();Logger.getLogger("org").setLevel(Level.ERROR);
emf = Persistence.createEntityManagerFactory("examplePersistenceUnit");
em = emf.createEntityManager();}
@After
publicvoid cleanup(){
em.close();}
@SuppressWarnings("unchecked")
@Test
publicvoid insertAndRetrieve(){
em.getTransaction().begin();
em.persist(p1);
em.persist(p2);
em.getTransaction().commit();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());for(Person current : list){finalString firstName = current.getFirstName();
assertTrue(firstName.equals("Brett")
|| firstName.equals("FirstName"));}}}
Re-run this test (the short-cut for this is Ctrl-Fll). Verify that everything is green.
Add an Embedded Entity
When we created Person we directly included address information into them. This is alright, but what if we want to use Address in another class? Let's introduce a new entity, Address, and make it embedded. This means its fields will end up as columns in the table of the entity that contains it.
Sure enough, if you review PersonTest.java, it no longer compiles. Before we go any further, let's update it to get it to compile and then verify that the unit tests still pass.
Replace the following two lines:
privatefinal Person p1 = new Person("Brett", 'L', "Schuchert", "Street1",
"Street2", "City", "State", "Zip");privatefinal Person p2 = new Person("FirstName", 'K', "LastName",
"Street1", "Street2", "City", "State", "Zip");
with the following four lines
privatefinal Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");privatefinal Person p1 = new Person("Brett", 'L', "Schuchert", a1);privatefinal Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");privatefinal Person p2 = new Person("FirstName", 'K', "LastName", a2);
Rerun your tests (Ctrl-F11) and make sure everything is all green.
Next, we want to verify that the address we persist is in the database. Update the unit test method as follows:
PersonTest#insertAndRetrieve
@SuppressWarnings("unchecked")
@Test
publicvoid insertAndRetrieve(){
em.getTransaction().begin();
em.persist(p1);
em.persist(p2);
em.getTransaction().commit();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());for(Person current : list){finalString firstName = current.getFirstName();finalString streetAddress1 = current.getAddress()
.getStreetAddress1();
assertTrue(firstName.equals("Brett")
|| firstName.equals("FirstName"));
assertTrue(streetAddress1.equals("A Rd.")
|| streetAddress1.equals("B Rd."));}}
Run your program and make sure it's all green.
Add an Entity with a One to Many Relationship
Now we'll make a company. In this first tutorial we're keeping things simple so we'll just create a Company that has a 1 to many relationship with People, who are its employees:
Update PersonTest.java to remove the two fields, emf and em and the initEmfAndEm() and cleanup() methods. PersonTest.java
packageentity;importstaticorg.junit.Assert.assertEquals;importstaticorg.junit.Assert.assertTrue;importjava.util.List;importorg.junit.Test;publicclass PersonTest extends TestBase {privatefinal Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");privatefinal Person p1 = new Person("Brett", 'L', "Schuchert", a1);privatefinal Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");privatefinal Person p2 = new Person("FirstName", 'K', "LastName", a2);
@SuppressWarnings("unchecked")
@Test
publicvoid insertAndRetrieve(){
em.getTransaction().begin();
em.persist(p1);
em.persist(p2);
em.getTransaction().commit();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());for(Person current : list){finalString firstName = current.getFirstName();finalString streetAddress1 = current.getAddress()
.getStreetAddress1();
assertTrue(firstName.equals("Brett")
|| firstName.equals("FirstName"));
assertTrue(streetAddress1.equals("A Rd.")
|| streetAddress1.equals("B Rd."));}}}
Make sure everything is green before going on (rerun using Ctrl-F11).
Now we need to create a new CompanyTest class. Here's the first version:
packageentity;importstaticorg.junit.Assert.assertEquals;importorg.junit.Test;publicclass CompanyTest extends TestBase {
@Test
publicvoid createCompany(){final Company c1 = new Company();
c1.setName("The Company");
c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
em.getTransaction().begin();
em.persist(c1);
em.getTransaction().commit();final Company foundCompany = (Company) em.createQuery("select c from Company c where c.name=?1").setParameter(1,
"The Company").getSingleResult();
assertEquals("D Rd.", foundCompany.getAddress().getStreetAddress1());// Note, we do not need an assert. Why? the method getSingleResult()// will throw an exception if there is not exactly one// object found. We'll research that in the second JPA tutorial.}}
Run this unit test and make sure it is all green before going on (right-click in the source pane, select Run As:JUnit Test).
If you'd like to run all of your tests, right-click on the test folder, select Run As:JUnit Test and eclipse will execute all of your tests classes' test methods.
Hire some people
We need to create some people and add them to the company. The PersonTest class already has some people. Rather than re-creating new people, let's update PersonTest to make those fields available. Update the a1, p1, a2, and p2 fields as follows:
publicstaticList<Person> generatePersonObjects(){finalList<Person> people = newArrayList<Person>();final Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");final Person p1 = new Person("Brett", 'L', "Schuchert", a1);final Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");final Person p2 = new Person("FirstName", 'K', "LastName", a2);
people.add(p1);
people.add(p2);return people;}
You will also need to update the beginning of the method insertAndRetrieve from:
finalList<Person> people = generatePersonObjects();
em.getTransaction().begin();for(Person p : people){
em.persist(p);}
em.getTransaction().commit();
Now we'll add a new test into CompanyTest to verify that we can hire people:
@SuppressWarnings("unchecked")
@Test
publicvoid createCompanyAndHirePeople(){final Company c1 = new Company();
c1.setName("The Company");
c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));List<Person> people = PersonTest.generatePersonObjects();for(Person p : people){
c1.hire(p);}
em.getTransaction().begin();for(Person p : people){
em.persist(p);}
em.persist(c1);
em.getTransaction().commit();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());final Company foundCompany = (Company) em.createQuery("select c from Company c where c.name=?1").setParameter(1,
"The Company").getSingleResult();
assertEquals(2, foundCompany.getEmployees().size());}
Update persistence.xml
Again, given our environment, this step is optional.
Now we're going to make sure the Person knows the Company for which it works. This is the "one" side of a one to many relationship. We need to explicitly set this value and map it. We also need to update the Company @OneToMany relationship so that the Entity Manager knows it is a bi-directional relationship rather than just two unidirectional relationships.
First we need to update Person: Person.java
publicclass Person {// .../**
* This relationship is optional. This means the database will allow this
* relationship to be null. It turns out that true is the default value so
* we only specify it to document its existence.
*/
@ManyToOne(optional = true)private Company job;public Company getJob(){return job;}publicvoid setJob(Company job){this.job = job;}// ...}
Next, we'll change Company to maintain both sides of the relationship:
publicclass Company {/**
* Adding mappedBy lets the entity manager know you mean for this
* relationship to be bi-directional rather that two unidirectional
* relationships.
*/
@OneToMany(mappedBy = "job")privateCollection<Person> employees;// update this methodpublicvoid setEmployees(finalCollection<Person> newStaff){// fire everybodyfinalCollection<Person> clone = newArrayList<Person>(employees);for(Person p : clone){
fire(p);}for(Person p : newStaff){
hire(p);}}// update this methodpublicvoid hire(final Person p){
getEmployees().add(p);
p.setJob(this);}// update this methodpublicvoid fire(final Person p){
getEmployees().remove(p);
p.setJob(null);}}
Before going any further, make sure all of your tests still run green.
We are now adding and removing Person objects from collections. To make this work, we need to add an equals() method and a hashCode() method to the Person class:
publicboolean equals(finalObject rhs){if(rhs instanceof Person){final Person other = (Person) rhs;return other.getLastName().equals(getLastName())&& other.getFirstName().equals(getFirstName())&& other.getMiddleInitial() == getMiddleInitial();}returnfalse;}publicint hashCode(){return getLastName().hashCode()* getFirstName().hashCode()* getMiddleInitial();}
Finally, we'll update CompanyTest in several stages:
First, add a utility method to retrieve companies by name:
private Company findCompanyNamed(final EntityManager em, String name){return(Company) em.createQuery("select c from Company c where c.name=?1")
.setParameter(1, name).getSingleResult();}
Add another support method to create a company and hire a few people:
private Company createCompanyWithTwoEmployees(){final Company c1 = new Company();
c1.setName("The Company");
c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));finalList<Person> people = PersonTest.generatePersonObjects();for(Person p : people){
c1.hire(p);}
em.getTransaction().begin();for(Person p : people){
em.persist(p);}
em.persist(c1);
em.getTransaction().commit();return c1;}
The method createCompany used to directly lookup a company by name. Update the test method to use this private method by changing this line:
final Company foundCompany = (Company) em.createQuery("select c from Company c where c.name=?1").setParameter(1,
"The Company").getSingleResult();
to:
final Company foundCompany = findCompanyNamed(em, "The Company");
Update the method createCompanyAndHirePeopl by using the support method createCompanyWithTwoEmployees():
@SuppressWarnings("unchecked")
@Test
publicvoid createCompanyAndHirePeople(){
createCompanyWithTwoEmployees();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());final Company foundCompany = (Company) em.createQuery("select c from Company c where c.name=?1").setParameter(1,
"The Company").getSingleResult();
assertEquals(2, foundCompany.getEmployees().size());}
Finally, add an additional unit test to hire and fire people:
@Test
publicvoid hireAndFire(){final Company c1 = createCompanyWithTwoEmployees();finalList<Person> people = PersonTest.generatePersonObjects();
em.getTransaction().begin();for(Person p : people){
c1.fire(p);}
em.persist(c1);
em.getTransaction().commit();final Company foundCompany = findCompanyNamed(em, "The Company");
assertEquals(0, foundCompany.getEmployees().size());}
Make sure everything compiles and is green.
Exercises
Questions
Describe what the @Entity annotation means to a class.
Describe what the @Id annotation means to a class.
Describe what the @GeneratedValue means to a class
Describe the difference between @Embeddable and @Embedded
In the previous section we mentioned using mappedBy to let the container know this was a bi-directional relationship instead of a unidirectional relationship. What does this even mean? Draw an instance diagram that would explain this difference.
New Class
Right now the relationship between Person and Company is direct. Let's make that a little less direct. The Person should now have a Job with a title, salary and employeeId. The company still has employees as before. This picture describes the before and after relationships.
In order to refactor to the [above] "after" picture consider some of the following advice:
Create Job
Create a class, Job, with the following attributes:
int id
String title
String Salary
String employeeNumber
Company company
Person person (added - fix diagram )
Update Employee
The Person class should no longer have a Company attribute. Instead it has a Job attribute.
Update Company
Update Company.hire(). Change the signature to take a person, title, salary. It will then create the job, generate an employeeId, and set up all the relationships and return the job.
Update the Company.fire() as necessary.
Advanced: Person can have multiple jobs
Make the relationship between Person and Job 1 to many. Now fix everything to make it work.
Advanced: First cut at a Data Access Object
If you look at your various unit tests, there are several places were they perform direct queries, inserts and removes. Move all of the code behind one or more Data Access Objects. Update your unit tests to use the Dao's.
packageentity;importstaticorg.junit.Assert.assertEquals;importjava.util.List;importjavax.persistence.EntityManager;importorg.junit.Test;publicclass CompanyTest extends TestBase {private Company findCompanyNamed(final EntityManager em, String name){return(Company) em.createQuery("select c from Company c where c.name=?1")
.setParameter(1, name).getSingleResult();}
@Test
publicvoid createCompany(){final Company c1 = new Company();
c1.setName("The Company");
c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
em.getTransaction().begin();
em.persist(c1);
em.getTransaction().commit();final Company foundCompany = findCompanyNamed(em, "The Company");
assertEquals("D Rd.", foundCompany.getAddress().getStreetAddress1());// Note, we do not need an assert. Why? the method getSingleResult()// will throw an exception if there is not exactly one// object found. We'll research that in the second JPA tutorial.}private Company createCompanyWithTwoEmployees(){final Company c1 = new Company();
c1.setName("The Company");
c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));finalList<Person> people = PersonTest.generatePersonObjects();for(Person p : people){
c1.hire(p);}
em.getTransaction().begin();for(Person p : people){
em.persist(p);}
em.persist(c1);
em.getTransaction().commit();return c1;}
@SuppressWarnings("unchecked")
@Test
publicvoid createCompanyAndHirePeople(){
createCompanyWithTwoEmployees();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());final Company foundCompany = (Company) em.createQuery("select c from Company c where c.name=?1").setParameter(1,
"The Company").getSingleResult();
assertEquals(2, foundCompany.getEmployees().size());}
@Test
publicvoid hireAndFire(){final Company c1 = createCompanyWithTwoEmployees();finalList<Person> people = PersonTest.generatePersonObjects();
em.getTransaction().begin();for(Person p : people){
c1.fire(p);}
em.persist(c1);
em.getTransaction().commit();final Company foundCompany = findCompanyNamed(em, "The Company");
assertEquals(0, foundCompany.getEmployees().size());}}
PersonTest.java
packageentity;importstaticorg.junit.Assert.assertEquals;importstaticorg.junit.Assert.assertTrue;importjava.util.ArrayList;importjava.util.List;importorg.junit.Test;publicclass PersonTest extends TestBase {publicstaticList<Person> generatePersonObjects(){finalList<Person> people = newArrayList<Person>();final Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");final Person p1 = new Person("Brett", 'L', "Schuchert", a1);final Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");final Person p2 = new Person("FirstName", 'K', "LastName", a2);
people.add(p1);
people.add(p2);return people;}
@SuppressWarnings("unchecked")
@Test
publicvoid insertAndRetrieve(){finalList<Person> people = generatePersonObjects();
em.getTransaction().begin();for(Person p : people){
em.persist(p);}
em.getTransaction().commit();finalList<Person> list = em.createQuery("select p from Person p")
.getResultList();
assertEquals(2, list.size());for(Person current : list){finalString firstName = current.getFirstName();finalString streetAddress1 = current.getAddress()
.getStreetAddress1();
assertTrue(firstName.equals("Brett")
|| firstName.equals("FirstName"));
assertTrue(streetAddress1.equals("A Rd.")
|| streetAddress1.equals("B Rd."));}}}
FAQ
JPA Tutorial 1 - FAQ
* What's the entity Manager?Object cache which persists entities. Manages any objects with the @Entity annotation. Allows starting/stopping transactions, crud'ing database. "Your facade to working with the database"
Is hibernate using an in-memory database?Hibernate is one implementation of JPA. It connects to the database through a jdbc url. The database we use in the early exercises is Hypersonic SQL.
When do you need a default constructor?Spec says you always need it. If there are no other constructors, java provides the default constructor. If you add a single constructor (w/parms), keep in mind the default constructor is removed by java.
Annotations?Annotations are type-safe meta-data about a class. They can target fields/methods/classes. Reflection allows you to query the state of any annotation. Annotations have 'lifetimes' (compile-time, load-time, run-time).
Compared to toplink: How are isolation levels handled? What kind of caching concerns do we have to worry about?There will be similiar challenges in tuning.
JEE source?Download from JBoss (svn) or get from Brett.
Where does @Id go? (method or field?) If it's on an attribute, JPA will only look for annotations on fields, and use reflection to access fields (without getter's and setter's). If it's on a method (getId()), JPA will only look for annotations on methods, and will use getter's and setter's to access fields.
Is there a reason @id is not private?Brett forgets things as he gets older. (It should be private)
When creating the job class, do we need equals() and hashcode()? not yet
In Company.java there is a lazy-initialization in getEmployees() . Why?When you retrieve from the db, hibernate creates an object with newInstance (reflection). Lazily initializing the employees collections could decrease unnecessary garbase collection when retrieving large result sets. This is just a consideration (not always the 'best' way).
What's with the equals() method on Person?You don't want an equals() who's value changes over time. (pre-insert, the id might be null, post-insert, it would have a value)
How many times does it go to the database when you have a one-to-many and you retrieve the 'one' side. By default, once for the root ('one') object, and then one or more times (up to the container's discretion) to retrieve the 'many' side -- as you iterate through the collection. You can override with fetch=FetchType.EAGER (to fetch with a join and retrieve everything up front). See the actual sql with hibernate.show_sql=true.
Can you turn off the 'caching' part of JPA?You can define transactional boundries to limit the scope of the cache. Look up 'report queries' for some examples.
How do you know what you set 'mappedBy' to? Use the name of the field that defines the relationship
What happens if you manipulate your objects outside of a transaction? In a JSE environment, objects are still managed by the EntityManager. So if you make a change to an object, then later start a transaction and commit it, the changes made outside of the transaction are also sent to the database. If, on the other hand, you close the EntityManager or flush it, the changes are lost.
Brett: What have you learned in this tutorial?
Class responses:
//
Eclipse is good
JPA hides a lot of JDBC complexity.
How to define relationships of entities
Using entity notations
//
Responses to questions in the exercises
What does @Entity do?
//
defines a class as being an entity bean.
Tells container what behavior to add through bytecode instrumentation or dynamic proxies
//
What does the @Id do ?
//
defines primary key
required for entities
only @Id fields can be passed into the em.find(class, id) method
//
What does the 'generatedValue' do ?
//
automatically assigns id based on the defined strategy
//
What's embeddable vs. embedded?
//
Defines how the data is stored in the db. (serialization vs fields being persisted to columns)
//
Day 1 Review
What we learned today . . .
JUnit
many-to-one & vice versa (bi-directional)
Annotations & JUnit
Eclipse
Entity Managers
hibernate
refactoring
eclipse shortcuts, embeddable stuff, understanding the relationships
eclipse shortcuts (!)
identifying annotation and attributes/properties
static imports
persisting all the entities (without using cascade attributes)
feedback so far on the class format
"better than just hearing somebody talk"
hands-on is good, maybe should have an overview at the beginning though. "pedagogical"
Table of Contents
Introduction
This first tutorial gives you an opportunity to work with:You'll write the code such that the environment creates the database schema based on meta-information (annotations) in your entity classes.
Good luck!
JPA Tutorial 1: Background
In this first tutorial we are going to perform some basic inserts, removes and queries against a database.JPA allows us to work with entity classes, which are denoted as such using the annotation @Entity or configured in an XML file (we'll call this persistence meta information). When we acquire the Entity Manager Factory using the Persistence class, the Entity Manager Factory finds and processes the persistence meta information.
To work with a database using JPA, we need an Entity Manager. Before we can do that, we need to create an Entity Manager Factory.
To acquire an Entity Manager Factory, we use the class javax.persistence.Persistence. It reads a file called persistence.xml in the META-INF directory. It then creates the named Entity Manager Factory, which processes persistence meta information stored in XML files or annotations (we only use annotations).
Creating an Entity Manager once we have the Entity Manager Factory is simple:
Once we have an Entity Manager, we can ask it to perform several operations such as persisting or removing an entity from the database or creating a query.
JPA Initial Setup
This example requires Java 5 (JDK 1.5) or later and Eclipse 3.2 or later. This page gives you a link to all of the downloads you'll need to get to get started. While I might mention specific version numbers, it's a good bet that newer versions should work as well... of course that's not always the case.Note: We need the jar file that contains javax.persistence and the various annotations associated with JPA. You can either download JEE or get it from somewhere else. For this series of tutorials, we eventually use the JBoss EJB3 Embeddable container, so we'll use that to avoid an extra 150+ meg download for one jar file.
Download Everything
First the basic stuff:Next, the libraries:
Setup the JDK & Eclipse
Extract Jar Files
Extract each of the libraries to some location. In my case I extracted everything to C:/libs, so I have the following directoriesEclipse Project Setup
Next we need to start eclipse and create a workspace.Create Initial Project
User Library
We are going to define a user library, which is just a collection of jar files with a name. Once we create this, we can add it to our classpath with one command. This also makes setting up new projects in the same workspace a snap. We can also export workspace settings and import them into a new workspace.Now we need to add several jars to this list. For each of the following jars, do the following:
Here is a list of all the jar files you'll need to add (note the path's listed assume you extracted your jar files to C:/libs):
Create Java Project
Next we need to create a Java project. We'll keep the source separate from the bin directory:Create folders and packages
Add Required Libraries
We now need to add two libraries. One will be the user-defined library we created above. The second will be JUnit 4.x.Sidebar
The JPA specification says that in a managed environment (read as running in the container), you do not need to list your entity classes in the persistence.xml (this is coming up). When you're using JPA in a JSE environment, this is not guaranteed. In all of these examples, we're using the hibernate implementation of the JEE Entity Manager. It provides the functionality of automatically registering all of your entities without your having to explicitly list them. However, if you happen to have a space in your class path, it appears to fail.If you followed the instructions above, you'll have the following directory: C:/Workspaces/JpaAndEjb3/JpaTutorial1. Under that directory will be a bin directory where our compiled class files reside. Hibernate will look in this directory and find all classes that are entities (denoted with @Entity) and add those to our persistent unit. In the first version of this tutorial, I recommended the following name: C:/Workspaces/Jpa And Ejb3/Tutorial 1. When I ran the driver program, hibernate was unable to automatically find the bin directory, I assume this was because of the spaces in the name. I changed the name by removing all of the spaces and the problem went away.
Persistence Unit Configuration
We now need to create the Persistent Unit definition. Create a file called persistence.xml in the src/META-INF directory with the following contents:persistence.xml
The Steps
Verify This Works
Create Your First Entity
For this example we'll use a "top-down" approach. This means we'll create a Plain Old Java Object (POJO) with some annotations to indicate how we want JPA to persist it. We're letting the EntityManager take care of creating the tables in the database for us.Create a Simple Class
The following class contains everything you need to begin persisting it to a database:
Person.java
Update persistence.xml
Note, for our configuration this step is optional.If you use libraries provided exclusively by JBoss and Company, then you do not need to update your persistence.xml. If you are using another vendor or you want to make sure that your solution will work regardless of your persistence provider, add the following line to your persistence.xml:
Your updated persistence.xml is now:
Inserting and Querying
Now we need to update our unit test class, Person.java. We will have it insert two people, query and verify that the people we created are in the database:PersonTest.java
Re-run this test (the short-cut for this is Ctrl-Fll). Verify that everything is green.
Add an Embedded Entity
When we created Person we directly included address information into them. This is alright, but what if we want to use Address in another class? Let's introduce a new entity, Address, and make it embedded. This means its fields will end up as columns in the table of the entity that contains it.First, we'll create Address:
Address.java
Next, we need to update Person (doing so will cause our unit test class to no longer compile):
Person.java
Sure enough, if you review PersonTest.java, it no longer compiles. Before we go any further, let's update it to get it to compile and then verify that the unit tests still pass.
Replace the following two lines:
with the following four lines
Rerun your tests (Ctrl-F11) and make sure everything is all green.
Next, we want to verify that the address we persist is in the database. Update the unit test method as follows:
PersonTest#insertAndRetrieve
Run your program and make sure it's all green.
Add an Entity with a One to Many Relationship
Now we'll make a company. In this first tutorial we're keeping things simple so we'll just create a Company that has a 1 to many relationship with People, who are its employees:Company.java
Factor out Common Test Code
We have some common initialization we can move up into a base since we are going to have two tests classes, PersonTest and CompanyTest:TestBase.java
Update PersonTest.java to remove the two fields, emf and em and the initEmfAndEm() and cleanup() methods.
PersonTest.java
Make sure everything is green before going on (rerun using Ctrl-F11).
Now we need to create a new CompanyTest class. Here's the first version:
Run this unit test and make sure it is all green before going on (right-click in the source pane, select Run As:JUnit Test).
If you'd like to run all of your tests, right-click on the test folder, select Run As:JUnit Test and eclipse will execute all of your tests classes' test methods.
Hire some people
We need to create some people and add them to the company. The PersonTest class already has some people. Rather than re-creating new people, let's update PersonTest to make those fields available. Update the a1, p1, a2, and p2 fields as follows:You will also need to update the beginning of the method insertAndRetrieve from:
to:
Now we'll add a new test into CompanyTest to verify that we can hire people:
Update persistence.xml
Again, given our environment, this step is optional.persistence.xml
Make sure everything compiles and runs green.
Make a Relationship Bi-Directional
Now we're going to make sure the Person knows the Company for which it works. This is the "one" side of a one to many relationship. We need to explicitly set this value and map it. We also need to update the Company @OneToMany relationship so that the Entity Manager knows it is a bi-directional relationship rather than just two unidirectional relationships.First we need to update Person:
Person.java
Next, we'll change Company to maintain both sides of the relationship:
Before going any further, make sure all of your tests still run green.
We are now adding and removing Person objects from collections. To make this work, we need to add an equals() method and a hashCode() method to the Person class:
Finally, we'll update CompanyTest in several stages:
First, add a utility method to retrieve companies by name:
Add another support method to create a company and hire a few people:
The method createCompany used to directly lookup a company by name. Update the test method to use this private method by changing this line:
to:
Update the method createCompanyAndHirePeopl by using the support method createCompanyWithTwoEmployees():
Finally, add an additional unit test to hire and fire people:
@Test public void hireAndFire() { final Company c1 = createCompanyWithTwoEmployees(); final List<Person> people = PersonTest.generatePersonObjects(); em.getTransaction().begin(); for (Person p : people) { c1.fire(p); } em.persist(c1); em.getTransaction().commit(); final Company foundCompany = findCompanyNamed(em, "The Company"); assertEquals(0, foundCompany.getEmployees().size()); }Make sure everything compiles and is green.
Exercises
Questions
New Class
Right now the relationship between Person and Company is direct. Let's make that a little less direct. The Person should now have a Job with a title, salary and employeeId. The company still has employees as before. This picture describes the before and after relationships.In order to refactor to the [above] "after" picture consider some of the following advice:
Create Job
Create a class, Job, with the following attributes:
Update Employee
The Person class should no longer have a Company attribute. Instead it has a Job attribute.
Update Company
Update Company.hire(). Change the signature to take a person, title, salary. It will then create the job, generate an employeeId, and set up all the relationships and return the job.
Update the Company.fire() as necessary.
Advanced: Person can have multiple jobs
Make the relationship between Person and Job 1 to many. Now fix everything to make it work.Advanced: First cut at a Data Access Object
If you look at your various unit tests, there are several places were they perform direct queries, inserts and removes. Move all of the code behind one or more Data Access Objects. Update your unit tests to use the Dao's.Entire Source Base
Address.javaPerson.java
Company.java
TestBase.java
CompanyTest.java
PersonTest.java
FAQ
JPA Tutorial 1 - FAQ
* What's the entity Manager? Object cache which persists entities. Manages any objects with the @Entity annotation. Allows starting/stopping transactions, crud'ing database. "Your facade to working with the database"Brett: What have you learned in this tutorial?
Class responses://
- Eclipse is good
- JPA hides a lot of JDBC complexity.
- How to define relationships of entities
- Using entity notations
//Responses to questions in the exercises
- What does @Entity do?
//- defines a class as being an entity bean.
- Tells container what behavior to add through bytecode instrumentation or dynamic proxies
//- What does the @Id do ?
//- defines primary key
- required for entities
- only @Id fields can be passed into the em.find(class, id) method
//- What does the 'generatedValue' do ?
//- automatically assigns id based on the defined strategy
//- What's embeddable vs. embedded?
//- Defines how the data is stored in the db. (serialization vs fields being persisted to columns)
//Day 1 Review
What we learned today . . .
feedback so far on the class format
What we'll do tommorrow
Links to the individual pages:
JPA Tutorial 1 - Background
JPA Tutorial 1 - Initial Setup
JPA Tutorial 1 - Eclipse Project Setup
JPA Tutorial 1 - Persistence Unit
JPA Tutorial 1 - First Entity
JPA Tutorial 1 - Embedded Entity
JPA Tutorial 1 - Entity with One to Many Relationship
JPA Tutorial 1 - Make Relationship Bi-directional
JPA Tutorial 1 - Exercises
JPA Tutorial 1 - Entire Source Base
JPA Tutorial 1 - FAQ
<--Back